#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#define SIZE 512
#define NUM 32
#define ZERO '\0'
#define SIGN " "

#define SKIP_PATH(pwd)          \
    do                          \
    {                           \
        pwd += strlen(pwd) - 1; \
        while (*pwd != '/')     \
            pwd--;              \
    } while (0)
#define SKIP_SPACE(userCommand, i)    \
    do                                \
    {                                 \
        while (userCommand[i] == ' ') \
            i++;                      \
    } while (0)
#define NONE_REDIRECT 0
#define IN_REDIRECT 1
#define OUT_REDIRECT 2
#define APPEND_REDIRECT 3
int redirectType = NONE_REDIRECT;
const char *fileName = NULL;
char *Argv[NUM];
char pwd[SIZE];
char oldPwd[SIZE];
int lastCode;

const char *GetLogName()
{
    return getenv("LOGNAME");
}
void GetHostName(char *hostName, int size)
{
    gethostname(hostName, size);
}
const char *GetPwd()
{
    return getenv("PWD");
}
const char *GetOldPWD()
{
    return getenv("OLDPWD");
}
void MakeCommandLineAndPrint()
{
    char commandLine[SIZE];
    char *logName = GetLogName();
    char hostName[SIZE];
    GetHostName(hostName, SIZE);

    char *pwd = GetPwd();
    SKIP_PATH(pwd);
    snprintf(commandLine, SIZE, "%s@%s:%s%c ", logName, hostName, strlen(pwd) == 1 ? pwd : pwd + 1, strcmp(logName, "root") == 0 ? '#' : '$');
    printf("%s", commandLine);
    fflush(stdout);
}
int GetUserCommand(char command[])
{
    if (fgets(command, SIZE, stdin) == NULL)
    {
        return -1;
    }
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}
void SplitCommand(char userCommand[])
{
    Argv[0] = strtok(userCommand, SIGN);
    int index = 1;
    while (Argv[index++] = strtok(NULL, SIGN))
        ;
}
void ExecuteCommand()
{
    int id = fork();

    if (id < 0)
    {
        perror("fork");
        exit(1);
    }
    else if (id == 0)
    {
        // child
        // 重定向
        if (redirectType == IN_REDIRECT)
        {
            // 打开文件
            int fd = open(fileName, O_RDONLY);
            if (fd < 0)
            {
                perror("IN_REDIRECT:open");
                exit(errno);
            }
            dup2(fd, 0);
        }
        else if (redirectType == OUT_REDIRECT)
        {
            // 打开文件
            int fd = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            if (fd < 0)
            {
                perror("OUT_REDIRECT:open");
                exit(errno);
            }
            dup2(fd, 1);
        }
        else if (redirectType == APPEND_REDIRECT)
        {
            // 打开文件
            int fd = open(fileName,  O_WRONLY | O_CREAT | O_APPEND, 0666);
            if (fd < 0)
            {
                perror("APPEND_REDIRECT:open");
                exit(errno);
            }
            dup2(fd, 1);
        }
        execvp(Argv[0], Argv);
        exit(errno);
    }
    else
    {
        // parent
        int status = 0;
        int rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            lastCode = WEXITSTATUS(status);
            if (lastCode)
            {
                printf("%s:%d:%s\n", Argv[0], lastCode, strerror(lastCode));
            }
        }
    }
}
char *GetHome()
{
    return getenv("HOME");
}

void Cd()
{
    char *path = Argv[1];
    if (path == NULL || strcmp(Argv[1], "~") == 0)
    {
        path = GetHome();
    }
    else if (strcmp(Argv[1], "-") == 0)
    {
        path = GetOldPWD();
    }

    chdir(path);

    // 刷新环境变量
    char tmp[SIZE];
    getcwd(tmp, SIZE);
    // 必须要先设置oldPwd，再设置pwd
    // 因为在你第一次执行cd命令的时候，你全局变量oldPwd，pwd就和环境变量表绑定了
    // 所谓的环境变量表，实际上就是一个char* 的数组，也就是char** ，数组的每一个元素都是char*，指向另一个字符串
    // 而在第一次的cd的时候，通过putenv(pwd),putenv(oldPwd)，将环境变量表中的两个元素指向分别指向了pwd和oldPwd

    // 而在第二次执行cd - 的时候，你如果执行 snprintf(pwd, SIZE,"PWD=%s", tmp); 修改了pwd的内容，
    // 那么你在执行GetPwd()函数值获得的内容是修改后的值，所以出现问题了
    snprintf(oldPwd, SIZE, "OLDPWD=%s", GetPwd());
    snprintf(pwd, SIZE, "PWD=%s", tmp);

    putenv(oldPwd);
    putenv(pwd);
}
int CheckBuiltInCommand()
{
    int flag = 0;
    if (strcmp(Argv[0], "cd") == 0)
    {
        flag = 1;
        Cd();
        lastCode = 0;
    }
    else if (strcmp(Argv[0], "echo") == 0 && strcmp(Argv[1], "$?") == 0)
    {
        flag = 1;
        printf("%d\n", lastCode);
        lastCode = 0;
    }
    return flag;
}
void CheckRedirect(char userCommand[])
{
    int n = strlen(userCommand);
    for (int i = 0; i < n; ++i)
    {
        if (userCommand[i] == '>')
        {
            userCommand[i] = ZERO;
            i++;
            redirectType = OUT_REDIRECT;
            if (userCommand[i] == '>')
            {
                i++;
                redirectType = APPEND_REDIRECT;
            }
            SKIP_SPACE(userCommand, i);
            fileName = userCommand + i;
            break;
        }
        else if (userCommand[i] == '<')
        {
            userCommand[i] = ZERO;
            i++;
            redirectType = IN_REDIRECT;
            SKIP_SPACE(userCommand, i);
            fileName = userCommand + i;
            break;
        }
    }
}
void Initialize()
{
    fileName = NULL;
    redirectType = NONE_REDIRECT;
}
int main()
{
    while (1)
    {
        Initialize();

        // 1. 制作命令行提示符
        MakeCommandLineAndPrint();

        // 2. 读取用户命令
        char userCommand[SIZE];
        int n = GetUserCommand(userCommand);
        if (n < 0)
        {
            return 1;
        }
        else if (n == 0)
            continue;
        // 2.1 检查重定向
        CheckRedirect(userCommand);
        // 2.2 test CheckRedirect
        //  printf("userCommand:%s\n", userCommand);
        //  printf("redirectType:%d\n", redirectType);
        //  printf("fileName:%s\n", fileName);
        // 3. 处理用户输入的命令
        SplitCommand(userCommand);
        // 4. 检测是否是内建命令
        if (CheckBuiltInCommand())
        {
            continue;
        }
        // n. 执行命令
        ExecuteCommand();
    }

    return 0;
}